package org.ttjhome.html.struts.common;

import javax.servlet.http.HttpServletRequest;

/**
 * ScopeLocal encapsulates the idea of a variable that is local to
 * some servlet scope - request, session, or application. It is used just
 * like {@link ThreadLocal}, except that the APIs need a reference to an
 * {@link HttpServletRequest}, for obvious reasons.
 *
 * <p>Usage example:
 * <pre>private static SessionLocal&lt;Foo&gt; gFoo = new SessionLocal&lt;Foo&gt;();
 * ...
 * Foo myFoo = gFoo.get(request);
 * </pre>
 *
 * @see ThreadLocal
 * @see ApplicationLocal
 * @see SessionLocal
 * @see RequestLocal
 *
 * 
 */
public abstract class ScopeLocal<T>
{
	/** prefix for automatically generated keys */
	private static final String KEY_PREFIX = "org.ttjhome.struts.common.ScopeLocal.";

	/** attribute key */
	protected String mKey;


	protected ScopeLocal(String key)
	{
		if (key == null)
		{
			// Calculate a unique attribute key. The key doesn't have to be
			// universally unique, just unique within this JVM.
			mKey = KEY_PREFIX + System.identityHashCode(this);
		}
		else
		{
			mKey = key;
		}
	}

	/**
	 * Returns the current scope's copy of this variable. Creates and
	 * initializes the variable if this is the first time we accessed it.
	 *
	 * @param request current servlet request
	 * @return current scope's value of the variable
	 */
	public T get(HttpServletRequest request)
	{
		T obj;
		synchronized (getLock(request))
		{
			obj = doGet(request);

			if (obj == null)
			{
				obj = initialValue(request);
				if (obj != null)
				{
					set(request, obj);
				}
			}
		}

		return obj;
	}

	/**
	 * Sets the current scope's copy of this variable to the specified value.
	 *
	 * @param request current servlet request
	 * @param value new value to be stored in the specified scope
	 */
	public void set(HttpServletRequest request, T value)
	{
		synchronized (getLock(request))
		{
			if (value == null)
			{
				remove(request);
				return;
			}

			doSet(request, value);
		}
	}

	/**
	 * Removes the variable from the specified scope. If the variable is
	 * accessed again, it will have its {@link #initialValue} again.
	 *
	 * @param request current servlet request
	 */
	public void remove(HttpServletRequest request)
	{
		synchronized (getLock(request))
		{
			doRemove(request);
		}
	}

	/**
	 * Returns the initial value for this scope-local variable. This method
	 * will be invoked the first time the variable is accessed with
	 * {@link #get}.
	 *
	 * <p>The default implementation returns <code>null</code>. If you want
	 * the value to be initialized to something else, you should subclass
	 * ScopeLocal and override this method.
	 *
	 * <p>If your implementation returns <code>null</code>, it will be invoked
	 * every time {@link #get} is called, until it returns a non-null value.
	 *
	 * <p>This method will be invoked again if the value is subsequently
	 * {@link #remove}d from its scope.
	 *
	 * <p>Feel free to invoke slow operations (e.g. remote besl calls) from
	 * inside of this method. You won't block other request- or session-scoped
	 * instances that aren't being invoked from the same request or session.
	 *
	 * @param request current servlet request
	 * @return initial value for this variable
	 */
	protected T initialValue(HttpServletRequest request)
	{
		return null;
	}

	/**
	 * Hook for subclasses to return a lock object which is specific to its
	 * scope. {@link #get}, {@link #set}, and {@link #remove} will all
	 * synchronize against this object.
	 *
	 * @param request current servlet request
	 * @return scope-specific lock object
	 */
	protected abstract Object getLock(HttpServletRequest request);

	/**
	 * Hook for scope-specific subclass implementation. Subclasses should
	 * retrieve the current value from the appropriate scope.
	 *
	 * @param request current servlet request
	 * @return value from subclass's scope
	 */
	protected abstract T doGet(HttpServletRequest request);

	/**
	 * Hook for scope-specific subclass implementation. Subclasses set the
	 * specified value into their scope.
	 *
	 * @param request current servlet request
	 * @param value value to set
	 */
	protected abstract void doSet(HttpServletRequest request, T value);

	/**
	 * Hook for scope-specific subclass implementation. Subclasses should
	 * remove the current value from the appropriate scope.
	 *
	 * @param request current servlet request
	 */
	protected abstract void doRemove(HttpServletRequest request);
}
